using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Globalization;

namespace Mud.Common.Messages
{
    /// <summary>
    /// Default implementation which holds the common functionality
    /// to all messages. It adds the MessageTypeId as the first part of the message.
    /// </summary>
    public abstract class Message : IMessage
    {

        /// <summary>
        /// Represents the type of message so that it can be deserialized.
        /// </summary>
        /// <value></value>
        public int MessageTypeId
        {
            get { return _messageTypeId; }
        }
        private int _messageTypeId;

        

        /// <summary>
        /// Serializes the message for transmission.
        /// </summary>
        /// <param name="package">Package to use for serialization.</param>
        public void Serialize(BytePackage package)
        {
            package.Enqueue(_messageTypeId);
            OnSerialize(package);
        }

        /// <summary>
        /// Deserializes serialized data back to the message.
        /// </summary>
        /// <param name="package">Package to use for deserialization.</param>
        public void Deserialize(BytePackage package)
        {
            int messageTypeId = package.DequeueInt();
            if (messageTypeId != (int)_messageTypeId)
                throw new InvalidOperationException("Invalid message type.");
            OnDeserialize(package);

        }

        /// <summary>
        /// Overriden by child classes to perform serialization of its contents.
        /// </summary>
        /// <param name="package">Contains serialized data.</param>
        protected abstract void OnSerialize(BytePackage package);

        /// <summary>
        /// Overriden by child classes to perform deserialization of its contents.
        /// </summary>
        /// <param name="package">Contains serialized data.</param>
        protected abstract void OnDeserialize(BytePackage package);


        public override string ToString()
        {
            return ((MessageTypeIds)this._messageTypeId).ToString();
        }

        public Message(MessageTypeIds messageTypeId)
        {
            _messageTypeId = (int)messageTypeId;
        }

    }

    /// <summary>
    /// Message which does nothing, used for simple messages which have no parameters.
    /// </summary>
    public abstract class MessageEmpty : Message
    {
        protected override void OnSerialize(BytePackage composer)
        {
        }

        protected override void OnDeserialize(BytePackage composer)
        {
        }

        public MessageEmpty(MessageTypeIds messageTypeId) : base(messageTypeId) { }
    }


    /// <summary>
    /// Message which uses reflection to get all the fields of the child to serialize and deserialize.
    /// </summary>
    public abstract class MessageReflection : Message
    {
        protected override void OnSerialize(BytePackage package)
        {
            FieldInfo[] fields = this.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            foreach (FieldInfo field in fields)
            {
                object value = field.GetValue(this);
                if (field.FieldType == typeof(int))
                {
                    package.Enqueue((int)value);
                    continue;
                }
                if (field.FieldType.IsEnum)
                {
                    package.Enqueue((int)value);
                    continue;
                }
                if (field.FieldType == typeof(string))
                {
                    package.Enqueue((string)value);
                    continue;
                }
                IMessage valueAsMessage = value as IMessage;
                if (valueAsMessage != null)
                {
                    valueAsMessage.Serialize(package);
                    continue;
                }
                if (field.FieldType == typeof(double))
                {
                    package.Enqueue((double)value);
                    continue;
                }
                if (field.FieldType == typeof(float))
                {
                    package.Enqueue((float)value);
                    continue;
                }
                if (field.FieldType == typeof(bool))
                {
                    package.Enqueue((bool)value);
                    continue;
                }
                if (field.FieldType == typeof(long))
                {
                    package.Enqueue((long)value);
                    continue;
                }
                if (field.FieldType == typeof(byte))
                {
                    package.Enqueue((byte)value);
                    continue;
                }
                if (field.FieldType == typeof(DateTime))
                {
                    package.Enqueue((DateTime)value);
                    continue;
                }
                throw new InvalidOperationException("Unable to serialize type " + field.FieldType.Name + ".");
            }

        }

        protected override void OnDeserialize(BytePackage package)
        {
            FieldInfo[] fields = this.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            foreach (FieldInfo field in fields)
            {
                if (field.FieldType.IsEnum)
                {
                    field.SetValue(this, package.DequeueInt());
                    continue;
                }
                if (field.FieldType == typeof(int))
                {
                    field.SetValue(this, package.DequeueInt());
                    continue;
                }
                if (field.FieldType == typeof(string))
                {
                    field.SetValue(this, package.DequeueString());
                    continue;
                }
                if (typeof(IMessage).IsAssignableFrom(field.FieldType))
                {
                    IMessage message = (IMessage)MessageFactory.DeserializeMessage(package);
                    field.SetValue(this, message);
                    continue;
                }
                if (field.FieldType == typeof(double))
                {
                    field.SetValue(this, package.DequeueDouble());
                    continue;
                }
                if (field.FieldType == typeof(bool))
                {
                    field.SetValue(this, package.DequeueBool());
                    continue;
                }
                if (field.FieldType == typeof(long))
                {
                    field.SetValue(this, package.DequeueLong());
                    continue;
                }
                if (field.FieldType == typeof(float))
                {
                    field.SetValue(this, package.DequeueFloat());
                    continue;
                }
                if (field.FieldType == typeof(byte))
                {
                    field.SetValue(this, package.DequeueByte());
                    continue;
                }
                if (field.FieldType == typeof(DateTime))
                {
                    field.SetValue(this, package.DequeueDateTime());
                    continue;
                }
                throw new InvalidOperationException("Unable to serialize type " + field.FieldType.Name + ".");
            }
        }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(((MessageTypeIds)this.MessageTypeId).ToString());
            FieldInfo[] fields = this.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            foreach (FieldInfo field in fields)
            {
                builder.AppendFormat(", {0}={1}", field.Name, field.GetValue(this).ToString());
            }
            return builder.ToString();
        }

        public MessageReflection(MessageTypeIds messageTypeId) : base(messageTypeId) { }
    }
}
